#   This program is free software; you can redistribute it and/or modify
#   it under the terms of the version 3 of the GNU Lesser General Public License
#   as published by the Free Software Foundation.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU Lesser General Public License
#   along with this program; if not, write to the Free Software
#   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Copyright (c) NEC Deutschland GmbH, NEC HPC Europe
#
# $Id$

from copy import copy, deepcopy
import logging
from aggmon.dp_aggregator import DPAggregator, DPAggregatorComponent
from basic_types.metric import Metric


STATE_ZERO = { "UNKNOWN": 0, "OK": 0, "WARNING": 0, "CRITICAL": 0 }
STATE_PRINT_ORDER = [ "CRITICAL", "WARNING", "OK", "UNKNOWN" ]


class GroupTristateCycleAggregator(DPAggregator):
    """
    Aggregates tri-state metrics (OK, WARNING, CRITICAL) to the worst one.
    Takes into account group metrics with its own name,
    i.e. aggregated results pushed from subgroups.
    WARNING! The handler must deal with metrics coming in from two
    subscriptions!

    This aggregator makes no sense without a hierarchy!
    
    Arguments:
       metric_name : should match a metric that is published inside the group
       agg_metric_name : name of the newly created metric
       group_path  : group hierarchy path (Example: '/siteB/rack12')
    """
    def __init__( self, metric_name, agg_metric_name, group_path, max_age=900, **kwds ):
        DPAggregator.__init__( self, metric_name, agg_metric_name, group_path, **kwds )
        assert self.getHierarchy() is not None, "Need a hierarchy for cyclic group aggregators"

        self.max_age = max_age
        self.group_seen_values = {}
        self.host_seen_value = {}
        self.last_time = None
        self.values = deepcopy( STATE_ZERO )
        group = self.getHierarchy().get_groups( group_path )[0]
        self.known_members = dict((key, None) for key in group.get_member_short_names() )
        self.expected_members = copy( self.known_members )


    def handleEvent( self, metric, topic=None ):
        if self.worthy_metric( metric, topic ):
            value = metric.value
        else:
            return
        if not value in STATE_PRINT_ORDER:
            return

        params = {}
        self._lock.acquire()

        try:
            topic_metric = topic.split(".")[-1]
            topic_host = topic.split(".")[-3]
            if topic_host in self.expected_members:
                del self.expected_members[topic_host]
            else:
                if not topic_host in self.known_members:
                    # didn't expect this guy, ignore it and return
                    logging.info( "unexpected topic_host in GroupTristateCycleAggregator: %s" % topic_host )
                    return

            if topic_metric == self.metric_name:
                self.values[value] += 1
                # seen this host before in the current cycle? replace it's value
                if topic_host in self.host_seen_value:
                    old_state = self.host_seen_value[topic_host]
                    self.values[old_state] -= 1
                self.host_seen_value[topic_host] = value

            elif topic_metric == self.agg_metric_name:
                new_states = {}
                for group_state in metric.output.split( ", " ):
                    if group_state.find( ":" ) == -1:
                        continue
                    state, number = group_state.split( ":" )
                    number = int(number)
                    try:
                        self.values[state] += number
                    except:
                        logging.error( "group_state=%s, number=%s" % (group_state, str(number)) )
                        raise
                    new_states[state] = number
                # seen this group before in the current cycle? replace it's values
                if topic_host in self.group_seen_values:
                    for old_state, number in self.group_seen_values[topic_host].items():
                        self.values[old_state] -= number
                self.group_seen_values[topic_host] = new_states

            if self.last_time is None:
                self.last_time = metric.time

            if len( self.expected_members.keys() ) == 0 \
                or (metric.time - self.last_time > self.max_age):
                #go_publish
                output_array = []
                worst_state = None
                for state in STATE_PRINT_ORDER:
                    if self.values[state] > 0:
                        worst_state = state
                        output_array.append( "%s:%d" % (state, self.values[state]) )
                if len( output_array ) > 0:
                    output = ", ".join( output_array )
                else:
                    worst_state = "UNKNOWN"
                    output = "no data received from group members"
                params = { "name": self.agg_metric_name,
                           "value": worst_state,
                           "output": output,
                           "source": "aggregator",
                           "time":  metric.time,
                           "host": self._group_topic }

                # initialize for next round
                self.values = deepcopy( STATE_ZERO )
                self.last_time = metric.time
                self.expected_members = copy( self.known_members )

        finally:
            self._lock.release()

        if len( params ) > 0:
            max_metric = Metric( **params )
            self.publish( max_metric )


class GroupTristateCycleAggregatorComponent(DPAggregatorComponent):
    def __init__( self, *args, **kwds ):
        DPAggregatorComponent.__init__( self, *args, **kwds)
        self.allowed_kwds.extend( ["max_age"] )

    def instantiate( self, *args, **kwds ):
        self.instance = None
        try:
            logging.debug( "instantiating: %s" % self.name )
            self.instance = GroupTristateCycleAggregator( *args, **kwds )
        except Exception, e:
            raise e
